/*
 *    Copyright © OpenAtom Foundation.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.inspur.edp.bef.bemanager.generatedbo;

import static com.inspur.edp.bef.bemanager.util.CheckInfoUtil.checkNull;

import com.inspur.edp.bef.bizentity.GspBizEntityElement;
import com.inspur.edp.bef.bizentity.GspBizEntityObject;
import com.inspur.edp.bef.bizentity.GspBusinessEntity;
import com.inspur.edp.bef.bizentity.beenum.BECategory;
import com.inspur.edp.bef.bizentity.common.SysnDboUtils;
import com.inspur.edp.cef.api.RefObject;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.element.GspElementDataType;
import com.inspur.edp.das.commonmodel.IGspCommonElement;
import com.inspur.edp.das.commonmodel.entity.GspCommonElement;
import com.inspur.edp.das.commonmodel.entity.object.GspCommonObjectType;
import com.inspur.edp.lcm.databaseobject.api.DatabaseObjectServiceForWebIde;
import com.inspur.edp.lcm.metadata.api.service.MetadataProjectService;
import com.inspur.edp.lcm.metadata.api.service.MetadataService;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.databaseobject.api.service.IDatabaseObjectRtService;
import java.util.UUID;
import lombok.var;

public class DboGenerater {

  private final String dboExceptionCode = "BeGenerateDbo";
  private final DatabaseObjectServiceForWebIde dtService = SpringBeanUtils
      .getBean(DatabaseObjectServiceForWebIde.class);
  private final MetadataProjectService projectService = SpringBeanUtils
      .getBean(MetadataProjectService.class);
  private static final IDatabaseObjectRtService databaseObjectRtService = SpringBeanUtils
      .getBean(IDatabaseObjectRtService.class);
  private final String projectPath;

  public DboGenerater(String metadataPath) {
    this.projectPath = projectService.getMetadataProjInfo(metadataPath).getProjectPath();
  }

  /**
   * 根据业务实体获取
   *
   * @param be be元数据实体
   */
  public final void GenerateDboFromBizEntity(GspBusinessEntity be, String path) {

    if (be.getCategory() == BECategory.DependentBusinessEntity) {
      return;
    }

    boolean isUsingTimeStamp = false;
    java.util.ArrayList<GspBizEntityObject> bizObjects = be.getAllNodes();
    for (GspBizEntityObject bizObject : bizObjects) {
      if (bizObject.isVirtual()) {
        continue;
      }
      String dboID = bizObject.getRefObjectName();
      if (!checkNull(dboID)) {
        dtService.isExistDatabaseObject(path, null, dboID);
        if (dtService.getDatabaseObjectById(path, dboID) instanceof DatabaseObjectView) {
          continue;
        }
        if (dtService.getDatabaseObjectById(path, dboID) instanceof DatabaseObjectTable) {
          DatabaseObjectTable dtDbo = (DatabaseObjectTable) dtService
              .getDatabaseObjectById(path, dboID);
          modifyDbo(bizObject, path, dtDbo, isUsingTimeStamp);
        } else {
          createDbo(bizObject, path, isUsingTimeStamp);
        }
      } else {
        createDbo(bizObject, path, isUsingTimeStamp);
      }
    }

  }


  private void createDbo(GspBizEntityObject bizObject, String path, boolean isUsingTimeStamp) {
    DatabaseObjectTable table = GenerateDboFromBizObject(bizObject, path,
        bizObject.getRefObjectName(), isUsingTimeStamp);
    bizObject.setRefObjectName(table.getId());
    dtService.saveDatabaseObjectTableWithoutFileName(path, table);
  }

  /**
   * 主对象获取当前be上设置的是否使用时间戳字段
   *
   * @param bizObject        当前对象
   * @param isUsingTimeStamp 是否生成时间戳字段
   * @return
   */
  private boolean GetIsUsingTimeStamp(GspBizEntityObject bizObject, boolean isUsingTimeStamp) {
    if (bizObject.getObjectType() == GspCommonObjectType.MainObject) {
      return isUsingTimeStamp;
    }
    return false;
  }

  /**
   * 根据业务实体对象创建dbo
   *
   * @param bizObject      当前对象
   * @param path            当前工程dbo文件路径
   * @param dboID          有值，则对应已有dbo但是无法找到的情况，此时新建原有dboID的dbo; 若为空，则完全新建dbo
   * @param usingTimeStamp 是否生成时间戳字段
   * @return
   */
  private DatabaseObjectTable GenerateDboFromBizObject(GspBizEntityObject bizObject, String path,
      String dboID, boolean usingTimeStamp) {
    String dboCode = GetDboNameWithAppCode(bizObject.getCode());
    String dboName = bizObject.getName();

    boolean isFiscalTable = false;
    boolean isUsingTimeStamp = GetIsUsingTimeStamp(bizObject, usingTimeStamp);

    checkDboFileNameRepeat(path, dboCode);
    checkDboTableNameRepeat(path, dboCode, dboID);

    DatabaseObjectTable table = new DatabaseObjectTable(dboCode, dboName, false, false,
        isUsingTimeStamp, isFiscalTable, getProjectBizObjectId(), dboID);
    table.setDescription("isSysGen");
    for (IGspCommonField element : bizObject.getContainElements()) {
      if (!element.getIsVirtual()) {
        addColumnByBizElementWithChildElements(
            (IGspCommonElement) ((element instanceof IGspCommonElement) ? element : null), table,
            bizObject.getIDElement().getID());

      }
    }
    return table;
  }

  /**
   * 根据业务实体字段新增列
   *
   * @param element 业务实体字段
   * @param table   DBO实体
   */
  private void addColumnByBizElementWithChildElements(IGspCommonElement element,
      DatabaseObjectTable table, String primaryKeyID) {
    var childElementsCount = element.getChildElements().size();

    if (childElementsCount == 0) {
      addColumn(element, table, primaryKeyID);
    } else if (childElementsCount == 1) {
      var childEle = (element.getChildElements().get(0) instanceof IGspCommonElement) ? element
          .getChildElements().get(0) : null;
      addColumn(element, table, primaryKeyID);
      childEle.setColumnID(element.getColumnID());
    } else {
      for (var GspCommonField : element.getChildElements()) {
        var childEle = GspCommonField;
        addColumn(childEle, table, primaryKeyID);
      }
    }
  }

  /**
   * @param element
   * @param table
   * @param primaryKeyID
   */
  private static void addColumn(IGspCommonElement element, DatabaseObjectTable table,
      String primaryKeyID) {
    String refEleID =
        checkNull(element.getColumnID()) ? UUID.randomUUID().toString() : element.getColumnID();
    element.setColumnID(refEleID);

    boolean isPrimaryKey = primaryKeyID.equals(element.getID());
    RefObject<Integer> length = new RefObject<Integer>(0);
    RefObject<Integer> precision = new RefObject<Integer>(0);
    RefObject<Integer> scale = new RefObject<Integer>(0);
    getLengthPrecisionScale(element, length, precision, scale);

    table.addColumn(refEleID, element.getLabelID(), element.getName(),
        getDataTypeByMDataType(element), length.argvalue, precision.argvalue, scale.argvalue, null,
        isPrimaryKey, isPrimaryKey, !isPrimaryKey, element.getIsMultiLanguage());
  }





  private void modifyDbo(GspBizEntityObject bizObject, String path, DatabaseObjectTable dbo,
      boolean isUsingTimeStamp) {

    DatabaseObjectTable table = modifyDboByBizObject(bizObject, dbo);

    dtService.saveDatabaseObjectTableWithoutFileName(path, table);
  }

  /**
   * 根据业务实体节点同步数据库对象（DBO）。
   *
   * @param bizEntityObject 要同步DBO的业务实体节点
   * @param boId            DBO所属的业务对象ID
   * @param usingTimeStamp  是否使用时间戳，使用时间戳的话，会更新DBO上的时间戳信息
   * @return 生成或者更新后的DBO对象
   */

  public static DatabaseObjectTable tryToGenOrModifyDbo(GspBizEntityObject bizEntityObject,
      String boId, boolean usingTimeStamp) {
    String dboId = bizEntityObject.getRefObjectName();
    DatabaseObjectTable databaseObjectTable = (DatabaseObjectTable) databaseObjectRtService
        .getDatabaseObject(dboId);
    boolean isDboNew = databaseObjectTable == null;
    if (isDboNew) {
      databaseObjectTable = new DatabaseObjectTable();
      String dboCode = SysnDboUtils.getDboNameWithAppCode(bizEntityObject.getCode(), boId);
      String dboName = bizEntityObject.getName();
      boolean isFiscalTable = false;
      boolean isUsingTimeStamp = SysnDboUtils.getIsUsingTimeStamp(bizEntityObject, usingTimeStamp);
      String dboID = bizEntityObject.getRefObjectName();
      if (dboID == null || "".equals(dboID)) {
        dboID = UUID.randomUUID().toString();
      }
      databaseObjectTable = new DatabaseObjectTable(dboCode, dboName, false, false,
          isUsingTimeStamp, isFiscalTable, boId, dboID);
      bizEntityObject.setRefObjectName(databaseObjectTable.getId());
    } else {
      if (databaseObjectTable == null) {
        throw new RuntimeException("如果传入的isDboNew是false，则传入的databaseObjectTable对象不能为空。");
      }
    }
    modifyDboByBizObject(bizEntityObject, databaseObjectTable);
    return databaseObjectTable;
  }

  /**
   * 根据业务实体对象修改数据库表
   *
   * @param bizObject 业务实体对象
   * @param table     数据库表
   * @return 数据库表
   */
  public static DatabaseObjectTable modifyDboByBizObject(GspBizEntityObject bizObject,
      DatabaseObjectTable table) {

    if (table == null) {
      throw new RuntimeException(String.format("不存在ID='%1$s'的数据库表。", bizObject.getRefObjectName()));
    }

    for (IGspCommonField field : bizObject.getContainElements()) {
      var ele = (GspBizEntityElement) field;
      if (!ele.getIsVirtual()) {
        modifyColumnByBizElementWithChildElements(ele, table, bizObject.getIDElement().getID());
      }
    }
    return table;
  }

  /**
   * 根据业务实体字段修改数据库表列 依照是否包含childElements，分情况讨论
   *
   * @param element      当前字段
   * @param table        数据库表
   * @param primaryKeyID 主键ID
   */
  private static void modifyColumnByBizElementWithChildElements(IGspCommonElement element,
      DatabaseObjectTable table, String primaryKeyID) {

    var childElementsCount = element.getChildElements().size();

    if (childElementsCount == 0) {
      modifyDboTableColumn(element, table, primaryKeyID);
    } else if (childElementsCount == 1) {
      modifyDboTableColumn(element, table, primaryKeyID);
      ((IGspCommonElement) element.getChildElements().get(0)).setColumnID(element.getColumnID());
    } else {
      for (var GspCommonField : element.getChildElements()) {

        var childEle = GspCommonField;
        modifyDboTableColumn(childEle, table, primaryKeyID);
      }
    }
  }

  /**
   * 根据业务实体字段修改数据库表中的列
   *
   * @param ele          当前字段
   * @param table        数据库表
   * @param primaryKeyID 主键ID
   */
  private static void modifyDboTableColumn(IGspCommonElement ele, DatabaseObjectTable table,
      String primaryKeyID) {
    if (!checkNull(((GspCommonElement) ele).getColumnID())) {
      DatabaseObjectColumn dboColumn = table.getColumnById(((GspCommonElement) ele).getColumnID());
      if (dboColumn != null) {
        modifyColumn(ele, table, primaryKeyID);
      } else {
        throw new RuntimeException(
            String.format("dbo上不存在ID='%1$s'列。", ((GspCommonElement) ele).getColumnID()));
      }
    } else {
      DatabaseObjectColumn dboColumn = table.getColumnByCode(ele.getLabelID());
      if (dboColumn != null) {
        throw new RuntimeException("dbo中已存在列" + ele.getLabelID() + ",请修改字段标签" + ele.getLabelID()
            + " 或 删除相应dbo列及其对应的数据库表中的列。");
      } else {
        addColumn(ele, table, primaryKeyID);
      }
    }
  }

  private static void modifyColumn(IGspCommonElement element, DatabaseObjectTable table,
      String primaryKeyID) {
    if (checkNull(element.getColumnID())) {
      throw new RuntimeException(String.format("字段%1$s无对应dbo列，应为新建，而非修改。", element.getName()));
    }
    boolean isPrimaryKey = primaryKeyID.equals(element.getID());
    RefObject<Integer> length = new RefObject<Integer>(0);
    RefObject<Integer> precision = new RefObject<Integer>(0);
    RefObject<Integer> scale = new RefObject<Integer>(0);
    getLengthPrecisionScale(element, length, precision, scale);

    DatabaseObjectColumn column = table.getColumnById(element.getColumnID());

    DataType dataType = getDataTypeByMDataType(element);
    boolean isUnique = column.isUnique();
    boolean isI18N = element.getIsMultiLanguage();
    switch (dataType) {
      case Varchar:
        if (column.getDataType() == DataType.NVarchar) {
          dataType = DataType.NVarchar;
        }
        break;
      case Clob:
        if (column.getDataType() == DataType.NClob) {
          dataType = DataType.NClob;
        }
        break;
      case Char:
        if (column.getDataType() == DataType.Boolean) {
          dataType = DataType.Boolean;
          length.argvalue = column.getLength();
          precision.argvalue = column.getPrecision();
          scale.argvalue = column.getScale();
        }
        break;
      case Int:
        if (element.getMDataType() == GspElementDataType.Integer
            && column.getDataType() == DataType.SmallInt) {
          dataType = DataType.SmallInt;
        }
        break;
    }

    var currentColumnDefaultValue = column.getDefaultValue();

    var columnLength =
        (length.argvalue > column.getLength()) ? length.argvalue : column.getLength();
    if (element.getMDataType() == GspElementDataType.Boolean) {
      columnLength = 1;
    }

    if (element.getMDataType() == GspElementDataType.Date
        && column.getDataType() == DataType.Varchar && column.getLength() == 8) {
      dataType = DataType.Varchar;
      columnLength = 8;
    }
    table.modifyColumn(element.getColumnID(), element.getName(), dataType, columnLength,
        precision.argvalue, scale.argvalue, currentColumnDefaultValue, isUnique, isPrimaryKey,
        column.isNullable(), isI18N);
  }



  /**
   * dbo重名校验，校验dbo文件名不重复
   *
   * @param dboName 对象编号
   * @param
   * @return
   */
  private void checkDboFileNameRepeat(String path, String dboName) {
    if (dtService.isDatabaseObjectFileExist(path, dboName)) {
      String message = "已存在名为[%1$s]的dbo文件,请修改对象编号。";
      throw new RuntimeException(String.format(message, dboName));
    }
  }

  /**
   * dbo重名校验，表名不重复
   *
   * @param dboName 对象编号
   * @return
   */
  private void checkDboTableNameRepeat(String path, String dboName, String dboID) {
    boolean tempVar = dtService.isExistDatabaseObject(path, dboName, dboID);
    if (tempVar) {
      String message = "已存在名为[%1$s]的数据库表,请修改对象编号。";
      throw new RuntimeException(String.format(message, dboName));
    }
  }

  /**
   * 解决lcm的长度精度小数位数与beElementDataType中长度精度小数位数中表达不一致问题
   *
   * @param element   业务实体字段
   * @param length    column中长度
   * @param precision column中精度
   * @param scale     column中小数位数
   */
  private static void getLengthPrecisionScale(IGspCommonElement element, RefObject<Integer> length,
      RefObject<Integer> precision, RefObject<Integer> scale) {
    length.argvalue = 0;
    precision.argvalue = 0;
    scale.argvalue = 0;

    switch (element.getMDataType()) {
      case Integer:
        length.argvalue = 0;
        precision.argvalue = 0;
        scale.argvalue = 0;
        break;
      case String:
        length.argvalue = element.getLength();
        precision.argvalue = 0;
        scale.argvalue = 0;
        break;
      case Decimal:
        length.argvalue = 0;
        precision.argvalue = element.getLength();
        scale.argvalue = element.getPrecision();
        break;
      case Boolean:
        length.argvalue = 1;
        precision.argvalue = 0;
        scale.argvalue = 0;
        break;
    }
  }

  /**
   * 字段类型转换
   *
   * @param ele 业务实体字段类型
   * @return 数据库对象字段类型
   */
  private static DataType getDataTypeByMDataType(IGspCommonElement ele) {
    GspElementDataType mDataType = ele.getMDataType();

    if (ele.getIsMultiLanguage()) {
      switch (mDataType) {
        case String:
          return DataType.NVarchar;
        case Text:
          return DataType.NClob;
        default:
          throw new RuntimeException(
              String.format("字段'%1$s'为多语字段，其数据类型应为[字符串]或[备注]。", ele.getName()));
      }
    }

    switch (mDataType) {

      case Integer:

        return DataType.Int;
      case Decimal:
        return DataType.Decimal;
      case String:
        return DataType.Varchar;
      case Boolean:
        return DataType.Char;
      case Text:
        return DataType.Clob;
      case Date:
        return DataType.DateTime;
      case DateTime:
        return DataType.TimeStamp;
      case Binary:
        return DataType.Blob;
      default:
        throw new RuntimeException(String.format("未定义的字段数据类型'%1$s'，无法转换为数据库对象字段类型。", mDataType));
    }
  }

  MetadataService metadataService = SpringBeanUtils.getBean(MetadataService.class);

  private String getAppCode() {
    return this.metadataService.getGspProjectInfo(projectPath).getAppCode();
  }

  private String getProjectBizObjectId() {
    return this.metadataService.getGspProjectInfo(projectPath).getBizobjectID();
  }

  /**
   * dbo编号添加[关键应用]前缀
   *
   * @param objCode
   * @return
   */
  private String GetDboNameWithAppCode(String objCode) {
    String appCode = getAppCode();
    return String.format("%1$s%2$s", appCode, objCode);
  }

}
